#include "CCameraSceneNode.h"
#include "ISceneNode.h"
#include "CSceneManager.h"
#include "CRenderDriverD3D11.h"



void ldx::scene::CCameraSceneNode::updateViewMatrix()
{
	ViewArea.getTransform(render::ETM_VIEW).buildCameraLookAtLH(RelativeTranslation, Target, UpVector);
}

void ldx::scene::CCameraSceneNode::updateProjectionMatrix()
{
	ViewArea.getTransform(render::ETM_PROJECTION).buildProjectionMatrixPerspectiveFovLH(Fovy, Aspect, ZNear, ZFar);
}

void ldx::scene::CCameraSceneNode::updateCameraMatrix()
{
	ViewArea.getTransform(render::ETM_CAMERA) = 
		ViewArea.getTransform(render::ETM_VIEW)*ViewArea.getTransform(render::ETM_PROJECTION);
}

ldx::scene::CCameraSceneNode::CCameraSceneNode(	ISceneNode * parent,CSceneManager * mgr, int id, const mt::vec3f & position, const mt::vec3f & lookat)
	: ICameraSceneNode(parent, mgr, id),
	ZNear(0.4f),ZFar(3000.f),
	Target(lookat),UpVector(0.f,1.f,0.f),
	InputReceiverEnabled(true),TargetAndRotationAreBound(false)
{
#ifdef _DEBUG
	setDebugName("CCameraSceneNode");
#endif
	Fovy = 45.f;
	render::IRenderDriver*  d = (mgr ? mgr->getRenderDriver() : 0);
	if (d)
		Aspect = (float)(d->getCurrentRenderTargetSize().X) /
		(float)d->getCurrentRenderTargetSize().Y;
	else
		Aspect = 4.0f / 3.0f;	// Aspect ratio.
	updateProjectionMatrix();
	updateViewMatrix();
	updateCameraMatrix();
}

void ldx::scene::CCameraSceneNode::setProjectionMatrix(const mt::mat4 & projection, bool isOrthogonal)
{
	IsOrthogonal = isOrthogonal;
	ViewArea.getTransform(render::ETM_PROJECTION) = projection;
}

ldx::scene::CCameraSceneNode::~CCameraSceneNode()
{

}

const mt::mat4 & ldx::scene::CCameraSceneNode::getProjectionMatrix() const
{
	return ViewArea.getTransform(render::ETM_PROJECTION);
}

const mt::mat4 & ldx::scene::CCameraSceneNode::getViewMatrix() const
{
	return ViewArea.getTransform(render::ETM_VIEW);
}

void ldx::scene::CCameraSceneNode::setViewMatrixAffector(const mt::mat4 & affector)
{
	Affector = affector;
}

const mt::mat4 & ldx::scene::CCameraSceneNode::getViewMatrixAffector() const
{
	return Affector;
}

bool ldx::scene::CCameraSceneNode::OnEvent(const SEvent & event)
{

	if (!InputReceiverEnabled)
		return false;

	// send events to event receiving animators

	ISceneNodeAnimatorList::iterator ait = Animators.begin();

	for (; ait != Animators.end(); ++ait)
		if ((*ait)->isEventReceiverEnabled() && (*ait)->OnEvent(event))
			return true;

	// if nobody processed the event, return false
	return false;
}

void ldx::scene::CCameraSceneNode::setTarget(const mt::vec3f & pos)
{
	Target = pos;
}

void ldx::scene::CCameraSceneNode::setRotation(const mt::vec3f & rotation)
{
	if (TargetAndRotationAreBound)
		Target = getAbsolutePosition() + rotation.rotationToDirection();

	ISceneNode::setRotation(rotation);
}

const mt::vec3f & ldx::scene::CCameraSceneNode::getTarget() const
{
	return Target;
}

void ldx::scene::CCameraSceneNode::setUpVector(const mt::vec3f & pos)
{
	UpVector = pos;
}

const mt::vec3f & ldx::scene::CCameraSceneNode::getUpVector() const
{
	return UpVector;
}

float ldx::scene::CCameraSceneNode::getNearValue() const
{
	return ZNear;
}

float ldx::scene::CCameraSceneNode::getFarValue() const
{
	return ZFar;
}

float ldx::scene::CCameraSceneNode::getAspectRatio() const
{
	return Aspect;
}

float ldx::scene::CCameraSceneNode::getFOV() const
{
	return Fovy;
}

void ldx::scene::CCameraSceneNode::setNearValue(float zn)
{
	ZNear = zn;
	updateProjectionMatrix();
	updateCameraMatrix();
}

void ldx::scene::CCameraSceneNode::setFarValue(float zf)
{
	ZFar = zf;
	updateProjectionMatrix();
	updateCameraMatrix();
}

void ldx::scene::CCameraSceneNode::setAspectRatio(float aspect)
{
	Aspect = aspect;
	updateProjectionMatrix();
	updateCameraMatrix();
}

void ldx::scene::CCameraSceneNode::setFOV(float fovy)
{
	Fovy = fovy;
	updateProjectionMatrix();
	updateCameraMatrix();
}

void ldx::scene::CCameraSceneNode::OnRegisterSceneNode()
{
	if (SceneManager->getActiveCamera() == this)
		SceneManager->registerNodeForRendering(this, ESNRP_CAMERA);

	ISceneNode::OnRegisterSceneNode();
}

void ldx::scene::CCameraSceneNode::render()
{
	mt::vec3f pos = getAbsolutePosition();
	mt::vec3f tgtv = Target - pos;
	tgtv.normalize();

	// if upvector and vector to the target are the same, we have a
	// problem. so solve this problem:
	mt::vec3f up = UpVector;
	up.normalize();

	float dp = tgtv.dotProduct(up);

	if (mt::equals(mt::abs_<float>(dp), 1.f))
	{
		up.X += 0.5f;
	}

	ViewArea.getTransform(render::ETM_VIEW).buildCameraLookAtLH(pos, Target, up);
	ViewArea.getTransform(render::ETM_VIEW) *= Affector;
	updateViewMatrix();
	updateCameraMatrix();
	render::IRenderDriver* driver = SceneManager->getRenderDriver();
	if (driver)
	{
		driver->setTransform(render::ETM_PROJECTION, ViewArea.getTransform(render::ETM_PROJECTION));
		driver->setTransform(render::ETM_VIEW, ViewArea.getTransform(render::ETM_VIEW));
		driver->setTransform(render::ETM_CAMERA, ViewArea.getTransform(render::ETM_CAMERA));
		driver->setViewPosition(pos);
		driver->setViewDirection(tgtv);
	}
}

const mt::aabbox3df & ldx::scene::CCameraSceneNode::getBoundingBox() const
{
	return ViewArea.getBoundingBox();
}

const ldx::scene::SViewFrustum * ldx::scene::CCameraSceneNode::getViewFrustum() const
{
	return &ViewArea;
}

void ldx::scene::CCameraSceneNode::setInputReceiverEnabled(bool enabled)
{
	InputReceiverEnabled = enabled;
}

bool ldx::scene::CCameraSceneNode::isInputReceiverEnabled() const
{
	return InputReceiverEnabled;
}

void ldx::scene::CCameraSceneNode::bindTargetAndRotation(bool bound)
{
	TargetAndRotationAreBound = bound;
}

bool ldx::scene::CCameraSceneNode::getTargetAndRotationBinding(void) const
{
	return TargetAndRotationAreBound;
}
